home *** CD-ROM | disk | FTP | other *** search
/ Visual Cafe 3 / Visual Cafe 3.ISO / Vcafe / Main.bin / JChart.java < prev    next >
Text File  |  1998-11-03  |  38KB  |  1,112 lines

  1. package com.symantec.itools.swing;
  2.  
  3. import com.sun.java.swing.*;
  4. import com.sun.java.swing.event.*;
  5. import com.sun.java.swing.table.*;
  6. import com.sun.java.swing.border.*;
  7. import java.beans.*;
  8. import java.awt.*;
  9. import java.awt.event.*;
  10. import java.util.*;
  11.  
  12. // ===================================================================================
  13. // = Revision History
  14. // ===================================================================================
  15.  
  16. //  09/25/98    MSH Added resource bundle for localization
  17. //  09/29/98    MSH    Added properties yAxisMin, yAxisMax
  18. //  09/30/98    MSH Re-wrote calibrateGraph() to improve support for negative numbers,
  19. //                  and to add support for yAxisMin, yAxisMax
  20. //  10/21/98    MSH Worked around JIT problem in form designer #67321
  21.  
  22.  
  23. /**
  24.  * Simple charting component.  Creates a chart capable of displaying one or more series
  25.  * of data in the form of a bar, line, scatter, pie, or area chart.
  26.  *
  27.  * The chart uses a standard TableModel interface as the data model type. The TableModel can
  28.  * use several types including: Integer, Double, Long, String, and DataElem
  29.  *
  30.  * @version 1.0, July 28, 1998
  31.  *
  32.  * @author  Michael Hopkins, Symantec
  33.  */
  34. public class JChart extends com.sun.java.swing.JComponent implements GraphStyle, TableModelListener
  35. {
  36.     /*** component parameters ***/
  37.     protected TableModel model;
  38.     protected int        graphStyle;      // the style of the graph
  39.     protected boolean    showLegend;      // whether legend should be displayed
  40.     protected boolean    showGrid;        // true if a grid should be drawn on the graph
  41.     protected String     title;           // title of the graph
  42.     protected int        precision;       // number of digits of precision to display for data
  43.     protected int        numTicksY;       // the number of ticks on the y axis
  44.     protected double     yAxisMin;        // the preferred starting point of the y axis
  45.     protected double     yAxisMax;        // the preferred maximum value on the y axis
  46.  
  47.     /*** internal data members ***/
  48.     protected double  graphMin;           // the smallest value in the data set
  49.     protected double  graphMax;           // the largest value in the data set
  50.     protected double  graphUpper;         // the upper bounds of the graph on the y axis
  51.     protected double  graphLower;         // the lower bounds of the graph on the y axis
  52.     protected int     graphCols;          // the size of the largest series (number of entries)
  53.     protected int     gridIncrementV;     // the number of pixels between major vertical subdivisions on the graph
  54.     protected int     gridIncrementH;     // the number of pixels between major horizontal subdivisions on the graph
  55.     protected double  gridStepV;          // the value per vertical tick on the y axis
  56.     protected int     labelYInset;        // additional inset on Y axis for labels
  57.     protected int     titleBorder;        // the space above the graph occupied by the title
  58.     protected int     legendBorder;       // the border
  59.     protected Insets  graphBorder;        // the border around the graph
  60.  
  61.     protected boolean debug;
  62.  
  63.     public static final double AUTO_CALCULATE_MIN = Double.NEGATIVE_INFINITY;
  64.     public static final double AUTO_CALCULATE_MAX = Double.POSITIVE_INFINITY;
  65.  
  66.     public JChart()
  67.     {
  68.         model = new  DefaultTableModel();
  69.         graphStyle = DEFAULT_STYLE;
  70.         showLegend = true;
  71.         showGrid = true;
  72.         title = "";
  73.         graphBorder = new Insets( 10, 5, 5, 5 );
  74.         precision = 0;
  75.         numTicksY = 5;
  76.  
  77.         graphMin = Double.MAX_VALUE;
  78.         graphMax = 0;
  79.         graphCols = 0;
  80.         gridIncrementV = 0;
  81.         gridIncrementH = 0;
  82.         gridStepV = 0;
  83.         labelYInset = 0;
  84.         legendBorder = 0;
  85.         yAxisMin = 0.0;
  86.         yAxisMax = AUTO_CALCULATE_MAX;
  87.         titleBorder = 0;
  88.  
  89.         debug = false;
  90.     }
  91.  
  92.     /**
  93.      * Gets the current model used by the chart
  94.      * @return the model
  95.      * @see #setModel
  96.      */
  97.     public TableModel getModel()
  98.     {
  99.         return model;
  100.     }
  101.  
  102.     /**
  103.      * Sets the current model used by the chart. The model is a TableModel derrivative, and can
  104.      *  be composed of objects of type Integer, Long, Double, String, or DataElem
  105.      * @param newModel the TabelModel to be used by the chart
  106.      * @exception IllegalArgumentException
  107.      * if the specified model is null
  108.      * @see #getModel
  109.      */
  110.     public void setModel( TableModel newModel )
  111.     {
  112.         TableModel oldModel = model;
  113.  
  114.         if (newModel == null)
  115.             throw new IllegalArgumentException("Cannot set a null TableModel");
  116.         if (newModel != oldModel)
  117.         {
  118.            if (oldModel != null)
  119.                 oldModel.removeTableModelListener(this);
  120.             model = newModel;
  121.             newModel.addTableModelListener(this);
  122.         }
  123.         graphCols = model.getRowCount();
  124.         graphMax = seriesMax();
  125.         graphMin = seriesMin();
  126.         repaint();
  127.     }
  128.  
  129.     /**
  130.      * Responds to notification of changes in the model data
  131.      * @param e the event to process
  132.      */
  133.     public void tableChanged(TableModelEvent e)
  134.     {
  135.         graphCols = model.getRowCount();
  136.         graphMax = seriesMax();
  137.         graphMin = seriesMin();
  138.         repaint();
  139.     }
  140.  
  141.     /**
  142.      * Get the preferred size of the component
  143.      * @return the dimension of the component
  144.      */
  145.     public Dimension getPreferredSize( )
  146.     {
  147.         Dimension d = new Dimension( 200, 150 );
  148.         return d;
  149.     }
  150.  
  151.     /**
  152.      * Get the minimum size of the component
  153.      * @return the minimum dimension of the component
  154.      */
  155.     public Dimension getMinimumSize()
  156.     {
  157.        Dimension d = new Dimension( 100, 75 );
  158.        return d;
  159.     }
  160.  
  161.     /**
  162.      * Sets the border of the component
  163.      */
  164.     public void setBorder( Border border )
  165.     {
  166.         super.setBorder( border );
  167.  
  168.         Insets i = getInsets();
  169.         graphBorder.top = 10 + i.top;
  170.         graphBorder.bottom = 5 + i.bottom;
  171.         graphBorder.left = 5 + i.left;
  172.         graphBorder.right = 5 + i.right;
  173.  
  174.         repaint();
  175.     }
  176.  
  177.   /**
  178.      * Draws the contents of the chart component
  179.      * @param g the graphics object to be referenced for drawing
  180.      */
  181.     public void paintComponent(java.awt.Graphics g)
  182.     {
  183.         TableModel model = getModel();
  184.         Dimension d = getSize();
  185.  
  186.         FontMetrics fm = g.getFontMetrics();
  187.  
  188.         if ( title.length() != 0 )
  189.             titleBorder = fm.getHeight() + 4;
  190.         else
  191.             titleBorder = 0;
  192.  
  193.         Insets i = getInsets();
  194.         graphBorder.top    = ((g.getFontMetrics().getHeight())/2) + i.top + titleBorder;
  195.         graphBorder.bottom = 5  + i.bottom;
  196.         graphBorder.left   = 5  + i.left;
  197.         graphBorder.right  = 5  + i.right;
  198.  
  199.         Color oldColor = g.getColor();
  200.         g.setColor( getBackground() );
  201.  
  202.         if ( graphStyle != PIE_STYLE )
  203.             g.fillRect( graphBorder.left + labelYInset, graphBorder.top,
  204.                         d.width - graphBorder.left - labelYInset - graphBorder.right - legendBorder ,
  205.                         d.height - graphBorder.bottom - graphBorder.top );
  206.         g.setColor( oldColor );
  207.  
  208.         calcLabelInset( g );        // calculations the position and dimensions of the legend
  209.         if ( showLegend == true && graphStyle != PIE_STYLE )
  210.             drawLegend( g );
  211.         drawTitle( g );
  212.         calibrateGraph( );          // determines the scale of the horizontal and vertical graph subdivisions
  213.  
  214.         drawGrid( g );
  215.                 // plot data
  216.         if ( graphStyle < PIE_STYLE ) // draw all types except pie charts and areas
  217.         {
  218.            int count = getNumSeries();      // the number of series to draw
  219.            for ( int k = 0; k < count; k++ )// draw each series
  220.            {
  221.               int tickPosH = graphBorder.left + labelYInset;    // the horizontal location of the series marker
  222.               int startPos = tickPosH;
  223.               g.setColor( getSeriesColor( k ));                 // draw in the current series color
  224.               int lastValue = 0;
  225.  
  226.               for ( int j = 0; j < getSeriesSize( k ); j++ )
  227.               {
  228.                 double  data  = getSeriesValue( k, j ) - graphLower;
  229.                 int     plotV = (int)(((data / gridStepV) * gridIncrementV) + .5 );
  230.  
  231.                 if ( count > 0 )
  232.                 {
  233.                     switch ( graphStyle )
  234.                     {
  235.                         case LINE_STYLE:
  236.                             if ( j > 0 )
  237.                                g.drawLine( startPos + (j-1) * gridIncrementH, d.height - lastValue - graphBorder.bottom  + 1,
  238.                                            startPos + j * gridIncrementH, d.height - plotV - graphBorder.bottom  +1 );
  239.  
  240.                         case SCATTER_STYLE:
  241.                             g.fillOval( tickPosH - 2,  d.height - plotV - graphBorder.bottom , 4, 4 );
  242.                             break;
  243.                         case BAR_STYLE:
  244.                             g.fillRect( tickPosH + k * (gridIncrementH / count)+2, d.height - plotV - graphBorder.bottom ,
  245.                                         (gridIncrementH / count) - 2, plotV );
  246.                             break;
  247.                         default:
  248.                             return;
  249.                     }
  250.                     lastValue = plotV;
  251.                     tickPosH += gridIncrementH;
  252.                 }
  253.               }
  254.            }
  255.         }
  256.         else
  257.             if ( graphStyle == AREA_STYLE )
  258.                 drawAreaGraph( g );
  259.             else
  260.                 drawPieGraph( g );
  261.  
  262.         if ( graphStyle != PIE_STYLE )
  263.             drawAxis( g );
  264.  
  265.         if ( Beans.isDesignTime() ) // work around for JIT problem
  266.             drawTitle( g );         // applet will work fine in browser
  267.     }
  268.  
  269.     /********** Property Accessor Routines **********/
  270.  
  271.     /**
  272.      * Gets the new graph style.
  273.      * @return the new border style, one of DEFAULT_STYLE, LINE_STYLE,
  274.      *    BAR_STYLE, PIE_STYLE, AREA_STYLE, or SCATTER_STYLE
  275.      * @see #setStyle
  276.      * @see GraphStyle#DEFAULT_STYLE
  277.      * @see GraphStyle#LINE_STYLE
  278.      * @see GraphStyle#BAR_STYLE
  279.      * @see GraphStyle#PIE_STYLE
  280.      * @see GraphStyle#AREA_STYLE
  281.      * @see GraphStyle#SCATTER_STYLE
  282.      */
  283.     public int getStyle()
  284.     {
  285.         return graphStyle;
  286.     }
  287.  
  288.     /**
  289.      * Sets the new graph style.
  290.      * @param style the new border style, one of DEFAULT_STYLE, LINE_STYLE,
  291.      *    BAR_STYLE, PIE_STYLE, AREA_STYLE, or SCATTER_STYLE
  292.      * @see #getStyle
  293.      * @see GraphStyle#DEFAULT_STYLE
  294.      * @see GraphStyle#LINE_STYLE
  295.      * @see GraphStyle#BAR_STYLE
  296.      * @see GraphStyle#PIE_STYLE
  297.      * @see GraphStyle#AREA_STYLE
  298.      * @see GraphStyle#SCATTER_STYLE
  299.      */
  300.     public void setStyle( int style )
  301.     {
  302.         if ( style <= AREA_STYLE == style >= DEFAULT_STYLE )
  303.         {
  304.             graphStyle = style;
  305.             repaint();
  306.         }
  307.     }
  308.  
  309.     /**
  310.      * returns whether or not the graph is to display a legend
  311.      * @return true if graph displays a legend
  312.      * @see #setShowLegend
  313.      */
  314.     public boolean isShowLegend()
  315.     {
  316.         return showLegend;
  317.     }
  318.  
  319.     /**
  320.      * Sets whether the graph is to display a legend or not
  321.      * @param b true if graph should display a legend
  322.      * @see #isShowLegend
  323.      */
  324.     public void setShowLegend( boolean b )
  325.     {
  326.         showLegend = b;
  327.         repaint();
  328.         if ( b == false )
  329.             legendBorder = 0;
  330.     }
  331.  
  332.     /**
  333.      * returns whether or not a grid is displayed as an overlay of the data area
  334.      * @return true if a grid is displayed
  335.      * @see #getShowGrid
  336.      */
  337.     public boolean isShowGrid( )
  338.     {
  339.         return showGrid;
  340.     }
  341.  
  342.    /**
  343.      * returns whether or not a grid is displayed as an overlay of the data area
  344.      * @return true if a grid is displayed
  345.      * @see #setShowGrid
  346.      * @deprecated use isShowGrid instead
  347.      */
  348.     public boolean getShowGrid( )
  349.     {
  350.         return isShowGrid();
  351.     }
  352.  
  353.     /**
  354.      * sets whether or not a grid should be displayed as an overlay of the data area
  355.      * @param show true if a grid should be displayed
  356.      * @see #getShowGrid
  357.      */
  358.     public void setShowGrid( boolean show )
  359.     {
  360.         showGrid = show;
  361.         repaint();
  362.     }
  363.  
  364.     /**
  365.      * Gets the title of the graph
  366.      * @return String title of the graph
  367.      * @see #setTitle
  368.      */
  369.     public String getTitle( )
  370.     {
  371.         return title;
  372.     }
  373.  
  374.     /**
  375.      * Sets the title of the graph
  376.      * @param s title of the graph
  377.      * @see #getTitle
  378.      */
  379.     public void setTitle( String s )
  380.     {
  381.         title = s;
  382.         repaint();
  383.     }
  384.  
  385.     /**
  386.      * gets border insets of the graph
  387.      * @return border of the graph
  388.      * @see #setGraphBorder
  389.      */
  390.     public Insets getGraphBorder( )
  391.     {
  392.         return graphBorder;
  393.     }
  394.  
  395.     /**
  396.      * sets the border insets of the graph
  397.      * @param i graph insets to use
  398.      * @see #getGraphBorder
  399.      */
  400.     public void setGraphBorder( Insets i )
  401.     {
  402.         graphBorder = i;
  403.     }
  404.  
  405.     /**
  406.      * gets the precision displayed by the chart. Precision is defined
  407.      * as the number of digits after the decimal point that are to be displayed.
  408.      * specifying zero indicates that the number is to be treated as an integer.
  409.      * @return int the number of places displayed after the decimal
  410.      * @see #setPrecision
  411.      */
  412.      public int getPrecision(  )
  413.      {
  414.         return precision;
  415.      }
  416.  
  417.     /**
  418.      * sets the precision to be displayed by the chart. Precision is defined
  419.      * as the number of digits after the decimal point that are to be displayed.
  420.      * specifying zero indicates that the number is to be treated as an integer.
  421.      * @param places the number of places to display after the decimal
  422.      * @see #getPrecision
  423.      */
  424.      public void setPrecision( int places )
  425.      {
  426.         precision = places;
  427.         repaint();
  428.      }
  429.  
  430.     /**
  431.      * gets the number of ticks or major subdivisions  displayed on the y axis
  432.      * @return int the number of major subdivisions of the y axis
  433.      * @see #setNumYTicks
  434.      */
  435.     public int getNumYTicks(  )
  436.     {
  437.         return numTicksY;
  438.     }
  439.  
  440.     /**
  441.      * sets the number of ticks or major subdivisions to be displayed on the y axis
  442.      * @param ticks the number of major subdivisions to use for the y axis
  443.      * @see #getNumYTicks
  444.      */
  445.     public void setNumYTicks( int ticks )
  446.     {
  447.         numTicksY = ticks;
  448.         repaint();
  449.     }
  450.  
  451.     /**
  452.      * gets the minimum or origin value on the y axis
  453.      * @return the minimum or origin value on the y axis
  454.      * @see #setYAxisMin
  455.      */
  456.     public double getYAxisMin()
  457.     {
  458.       return yAxisMin;
  459.     }
  460.  
  461.     /**
  462.      * sets the minimum or origin value on the y axis
  463.      * @param min the minimum or origin value on the y axis
  464.      * @see #getYAxisMin
  465.      */
  466.     public void setYAxisMin( double min )
  467.     {
  468.         yAxisMin = min;
  469.         repaint();  // calls calibrate graph
  470.     }
  471.  
  472.     /**
  473.      * gets the maximum on the y axis
  474.      * @return the maximum value on the y axis
  475.      * @see #setYAxisMax
  476.      */
  477.     public double getYAxisMax()
  478.     {
  479.        return yAxisMax;
  480.     }
  481.  
  482.    /**
  483.      * sets the maximum on the y axis
  484.      * @param max the maximum value on the y axis
  485.      * @see #getYAxisMax
  486.      */
  487.     public void setYAxisMax( double max )
  488.     {
  489.        yAxisMax = max;
  490.        repaint();  // calls calibrate graph
  491.     }
  492.  
  493.     /********** Data Acessor Routines **********/
  494.  
  495.     /**
  496.      * Returns the number of data series that are used in the graph
  497.      * @return number of series
  498.      */
  499.     public int getNumSeries( )
  500.     {
  501.         return getModel().getColumnCount();
  502.     }
  503.  
  504.     /**
  505.      * Gets the size (number of elements) of the series referenced by index i
  506.      * @param i the index of the series
  507.      * @return the size of the series
  508.      * @exception ArrayIndexOutOfBoundsException
  509.      */
  510.     public int getSeriesSize( int i )
  511.     {
  512.        return getModel().getRowCount();
  513.     }
  514.  
  515.     /**
  516.      * retrieves a series entry value
  517.      * @param series the number of the series to edit
  518.      * @param index  the index of the item to edit
  519.      * @exception ArrayIndexOutOfBoundsException
  520.      */
  521.     public double getSeriesValue( int series, int index )
  522.     {
  523.         Object obj = getModel().getValueAt(index, series);
  524.  
  525.         if ( obj != null )
  526.         {
  527.             try
  528.             {
  529.                 if ( obj instanceof DataElem )
  530.                     return ((DataElem)obj).getData();
  531.                 else if ( obj instanceof Double )
  532.                     return ((Double) obj).doubleValue();
  533.                 else if ( obj instanceof Integer )
  534.                     return (double)((Integer) obj).intValue();
  535.                 else if ( obj instanceof Long )
  536.                     return (double)((Long)obj).intValue();
  537.                 else if ( obj instanceof String )
  538.                     return (new Double((String)obj)).doubleValue();
  539.             }
  540.             catch ( Exception e )
  541.             {
  542.                 System.out.println( "Warning- Non-numeric detected in chart data: " + obj );
  543.             }
  544.         }
  545.         return 0;
  546.     }
  547.  
  548.     /**
  549.      * gets the name of a series associated with a given index
  550.      * @param series index of the series to use
  551.      * @return name of the requested series
  552.      */
  553.     public String getSeriesName( int series )
  554.     {
  555.         return getModel().getColumnName( series );
  556.     }
  557.  
  558.     /**
  559.      * gets the default color of a particular series
  560.      * @param series index of the series to use
  561.      * @return color associated with the requested series
  562.      */
  563.     public Color getSeriesColor( int series )
  564.     {
  565.         return getSeriesColor( series, 0 );
  566.     }
  567.     /**
  568.      * gets the color of a particular data element within a series
  569.      * @param series number of the series to use
  570.      * @param index associated with the data element within the series
  571.      * @return color associated with the requested series
  572.      */
  573.     public Color getSeriesColor( int series, int index )
  574.     {
  575.         Object obj = getModel().getValueAt(index, series);
  576.  
  577.         if ( obj != null )
  578.         {
  579.             if ( obj instanceof DataElem )
  580.                 return ((DataElem)obj).getColor();
  581.         }
  582.         Vector colors = DataElem.graphColors;
  583.         if ( graphStyle != PIE_STYLE )
  584.             return (java.awt.Color) colors.elementAt( series % colors.size());
  585.         return ( java.awt.Color ) colors.elementAt( ((series * graphCols) + index) % colors.size() );
  586.     }
  587.  
  588.     /**
  589.      * gets the label of a data element associated with a given
  590.      * index of a specific series
  591.      * @param series number of the series to use
  592.      * @param index of the data element to use
  593.      * @return name of the requested series
  594.      * @see #getSeriesName
  595.      */
  596.     public String getDataLabel( int series, int index )
  597.     {
  598.         Object obj = getModel().getValueAt(index, series);
  599.  
  600.         if ( obj != null )
  601.         {
  602.             if ( obj instanceof DataElem )
  603.                 return ((DataElem)obj).getLabel();
  604.         }
  605.         return "";
  606.     }
  607.  
  608.     /********** Data Statistical Routines **********/
  609.  
  610.     /**
  611.      * finds the highest value (max) in all series
  612.      * @return max value in given series
  613.      * @exception ArrayIndexOutOfBoundsException
  614.      * @see #seriesMax( int num )
  615.      */
  616.     protected double seriesMax(  )
  617.     {
  618.         double maxVal = 0;
  619.         for ( int i = 0; i < getNumSeries(); i++ )
  620.             maxVal = Math.max( maxVal, seriesMax( i ));
  621.         return maxVal;
  622.     }
  623.  
  624.     /**
  625.      * finds the highest value (max) in a series
  626.      * @param num the series in which to find the maximum value
  627.      * @return max value in given series
  628.      * @exception ArrayIndexOutOfBoundsException
  629.      */
  630.     protected double seriesMax( int num )
  631.     {
  632.         double maxVal = 0;
  633.         for ( int i = 0; i < getSeriesSize( num ); i++ )
  634.            maxVal = Math.max( maxVal, getSeriesValue( num, i ));
  635.         return maxVal;
  636.     }
  637.  
  638.      /**
  639.      * finds the lowest value (min) in all series
  640.      * @return min value in given series
  641.      * @exception ArrayIndexOutOfBoundsException
  642.      * @see #seriesMin( int num )
  643.      */
  644.     protected double seriesMin(  )
  645.     {
  646.         double minVal = Double.MAX_VALUE;
  647.         for ( int i = 0; i < getNumSeries(); i++ )
  648.             minVal = Math.min( minVal, seriesMin( i ));
  649.         return minVal;
  650.     }
  651.  
  652.     /**
  653.      * finds the lowest value (min) in a series
  654.      * @param num the series in which to find the minimum value
  655.      * @return min value in given series
  656.      * @exception ArrayIndexOutOfBoundsException
  657.      */
  658.     protected double seriesMin( int num )
  659.     {
  660.         double minVal = getSeriesValue( num, 0 );
  661.         for ( int i = 1; i < getSeriesSize( num ); i++ )
  662.            minVal = Math.min( minVal, getSeriesValue( num, i ));
  663.         return minVal;
  664.     }
  665.  
  666.     /**
  667.      * finds the maximum of the summation of all series entries at a given index
  668.      * @return maximum graph summation
  669.      */
  670.     protected double seriesSumMax( )
  671.     {
  672.         double max = 0;
  673.  
  674.         for ( int i = 0; i < graphCols; i++ )
  675.         {
  676.             double tempMax = 0;
  677.             for ( int j = 0; j < getNumSeries(); j++ )
  678.             {
  679.                 if ( i < getSeriesSize( j ) )
  680.                     tempMax += getSeriesValue( j, i );
  681.             }
  682.             max = Math.max( max, tempMax );
  683.         }
  684.         return max;
  685.     }
  686.  
  687.     /**
  688.      * finds the sumation of all data points in a given series
  689.      * @param num the series in which to find the sumation
  690.      * @return sumation of a given series
  691.      */
  692.     protected double seriesSum( int num )
  693.     {
  694.         double sum = getSeriesValue( num, 0 );
  695.         for ( int i = 1; i < getSeriesSize( num ); i++ )
  696.             sum += getSeriesValue( num, i );
  697.         return sum;
  698.     }
  699.  
  700.     /********** Graphics utility routines  **********/
  701.  
  702.     protected String truncDataStr( String s )
  703.     {
  704.         StringBuffer buf = new StringBuffer( s );
  705.  
  706.         int i = s.indexOf( '.' );
  707.         if ( i > 0 )
  708.             if ( precision == 0 )
  709.                 buf.setLength( i );
  710.             else if ( buf.length() >= i + 1+ precision )
  711.                 buf.setLength( i + 1 + precision );
  712.  
  713.         return buf.toString();
  714.     }
  715.  
  716.     protected void calcLabelInset( java.awt.Graphics g )
  717.     {
  718.         FontMetrics fm = g.getFontMetrics();
  719.         Insets i = getInsets();
  720.  
  721.         if ( graphStyle != AREA_STYLE )
  722.            labelYInset = fm.stringWidth( truncDataStr( String.valueOf( graphMax ))) - 1;
  723.         else
  724.             labelYInset = fm.stringWidth( truncDataStr( String.valueOf( seriesSumMax() ))) - 1;
  725.     }
  726.  
  727.     /**
  728.      * Calculates the interval between tick marks on the horizontal axis
  729.      */
  730.     protected void calcTickInterval()
  731.     {
  732.         if ( graphStyle == BAR_STYLE )
  733.             gridIncrementH =  (getSize().width - graphBorder.right - graphBorder.left - labelYInset - legendBorder )/
  734.                     (graphCols > 0 ? graphCols : 1);
  735.         else
  736.             gridIncrementH = (getSize().width - graphBorder.right - graphBorder.left - labelYInset - legendBorder )/
  737.                     (graphCols-1 > 0 ? graphCols-1 : 1);
  738.     }
  739.  
  740.  
  741.     protected void calibrateGraph( )     // find y axis calibration
  742.     {
  743.         calcTickInterval();
  744.  
  745.         if ( yAxisMax != AUTO_CALCULATE_MAX )
  746.             graphUpper = yAxisMax;
  747.         else
  748.         {
  749.             if ( graphStyle != AREA_STYLE )
  750.                 graphUpper = seriesMax();
  751.             else
  752.                 graphUpper = seriesSumMax();
  753.         }
  754.  
  755.         debugStr( "graphUpper: " + graphUpper );
  756.  
  757.         if ( precision == 0 )
  758.             gridStepV = Math.ceil(graphUpper/numTicksY);
  759.         else
  760.             gridStepV = graphUpper / numTicksY;
  761.  
  762.         debugStr( "gridStepV: " + gridStepV );
  763.  
  764.         if ( yAxisMax == AUTO_CALCULATE_MAX )
  765.             graphUpper = gridStepV * numTicksY;
  766.  
  767.         // if the user has provided an upper and a lower limit, use them
  768.         if ( yAxisMin != AUTO_CALCULATE_MIN )
  769.             graphLower = yAxisMin;
  770.         else
  771.         {
  772.             graphLower = seriesMin();
  773.             graphLower = Math.floor( graphLower/gridStepV ) * gridStepV;
  774.             if ( graphLower > 0 && (graphLower / ( seriesMax() - seriesMin()) <= .2 ))
  775.                 graphLower = 0;
  776.         }
  777.  
  778.         //gridStepV = (graphUpper - graphLower)/numTicksY;
  779.         Dimension d = getSize();
  780.         gridIncrementV= (int)(((d.height - graphBorder.bottom - graphBorder.top ) / numTicksY)+.5);
  781.      }
  782.  
  783.     protected void drawAreaGraph( java.awt.Graphics g )
  784.     {
  785.         Dimension d = getSize();
  786.         int    plotV = 0;
  787.         double lastValue = 0;
  788.         Vector polyArray = new Vector( getNumSeries() );
  789.  
  790.         int tickPosH = graphBorder.left + labelYInset;
  791.         for ( int i = 0; i < graphCols; i++ )
  792.         {
  793.             for ( int j = 0; j < getNumSeries(); j++ )
  794.             {
  795.                 if ( i == 0 )   // we only want to create a polygon once for each series
  796.                 {
  797.                     Polygon poly = new Polygon();
  798.                     poly.addPoint( graphBorder.left + labelYInset, d.height - graphBorder.bottom );
  799.                     polyArray.addElement((Object) poly);
  800.                 }
  801.                 if ( i < getSeriesSize( j ) )
  802.                 {
  803.                     double data  = getSeriesValue( j, i ) - graphLower;
  804.                     plotV = (int)(((data / gridStepV) * gridIncrementV) + .5 );
  805.                     plotV += lastValue;
  806.                 }
  807.  
  808.                 Polygon p = (Polygon) polyArray.elementAt( j );
  809.                 p.addPoint( tickPosH,  d.height - plotV - graphBorder.bottom );
  810.                 lastValue = plotV;
  811.             }
  812.             tickPosH += gridIncrementH;
  813.             plotV = 0;
  814.             lastValue = 0;
  815.         }
  816.  
  817.         for ( int k = getNumSeries()-1; k >= 0; k-- )
  818.         {
  819.             Polygon tempPoly = (Polygon) polyArray.elementAt( k );
  820.             tempPoly.addPoint( graphBorder.left + labelYInset + (graphCols-1) * gridIncrementH, d.height - graphBorder.bottom );
  821.             g.setColor( getSeriesColor( k ));
  822.             g.fillPolygon( tempPoly );
  823.         }
  824.     }
  825.  
  826.     protected void drawPieGraph( java.awt.Graphics g )
  827.     {
  828.         Dimension d = getSize();
  829.         Rectangle graphArea = new Rectangle( d.width - graphBorder.left - graphBorder.right,
  830.                                              d.height - graphBorder.top - graphBorder.bottom );
  831.         int minDimension = Math.min( graphArea.width, graphArea.height );
  832.  
  833.         if ( minDimension <= 0 )
  834.             return;
  835.  
  836.         Rectangle cell = new Rectangle( minDimension, minDimension );
  837.  
  838.         int num = getNumSeries();
  839.         
  840.         if ( num < 1 )
  841.             return;
  842.             
  843.         int rows = 1;
  844.         int cols = num;
  845.         int pad  = 30;
  846.         int rem  = 0;
  847.         int first= num;
  848.         FontMetrics fm = g.getFontMetrics();
  849.         int vLabel = fm.getHeight() + 10;
  850.         boolean done = false;
  851.  
  852.         cell.width = (int)Math.floor((graphArea.width - pad * ( num - 1 )) / (double) num); // fit cells horizontally
  853.         cell.height= (int)Math.floor((graphArea.height- pad * ( rows - 1 ))  / (double) rows - vLabel);
  854.         cell.width = cell.height = Math.min( cell.width, cell.height );
  855.         cell.height += vLabel;
  856.  
  857.         while ( !done )     // figure out how to tile the pie charts
  858.         {
  859.             if ( (rows+1) * cell.height + rows * pad < graphArea.height ) // is there room for another row?
  860.             {
  861.                 rows++;                     // create a new row
  862.                 cols = num / rows;          // compute average row count
  863.                 rem = num - (cols * rows);  // compute remainder
  864.                 if ( rem > 1  )
  865.                 {
  866.                     cols += 1;
  867.                     first = cols;
  868.                 }
  869.                 else
  870.                     first = cols + rem;
  871.  
  872.                 cell.width = (int)Math.floor((graphArea.width - pad * ( first - 1 )) / (double) first); // fit cells horizontally
  873.                 cell.height= (int)Math.floor((graphArea.height- pad * ( rows - 1 ))  / (double) rows - vLabel);
  874.                 cell.width = cell.height = Math.min( cell.width, cell.height );
  875.                 cell.height += vLabel;
  876.             }
  877.             else
  878.                 done = true;
  879.         }
  880.  
  881.         int n = 1;
  882.         int last = 0;
  883.         int hSpace = ((graphArea.width - ( cols * cell.width + ( cols - 1 ) * pad ))/cols)/2;
  884.         int vSpace = ((graphArea.height - ( rows * cell.height + ( rows - 1 ) * pad ))/rows)/2;
  885.         for ( int y = 0; y < rows; y++ )
  886.         {
  887.             for ( int x = 0; x < first; x++ )
  888.             {
  889.                 if ( n > num )
  890.                     break;
  891.  
  892.                 if ( y != 0 && x >= cols )
  893.                     continue;
  894.                 cell.setLocation( graphArea.x + graphBorder.left + ( cell.width + ((x != 0 ) ? pad : 0 )) *x + hSpace,
  895.                                   graphArea.y + graphBorder.top + (cell.height + ((y != 0 ) ? pad : 0 )) * y + vSpace );
  896.                 drawPieSeries( g, n-1, cell, last, vLabel );
  897.                 n++;
  898.             }
  899.             last = 0;
  900.         }
  901.     }
  902.  
  903.     /**
  904.      * Draws the legend of the graph
  905.      */
  906.     protected void drawLegend( java.awt.Graphics g )
  907.     {
  908.         int         maxStrLen = 0;                // the length of the longest series name string
  909.         Rectangle   lRect;                        // the rectangle of the legend
  910.         FontMetrics fm = g.getFontMetrics();      // the font metrics of the Graphics object
  911.         int         lnHeight = fm.getHeight();    // the height of a single line of text in the legend
  912.         int         boxHeight;                    // the height of the legend box including text str
  913.         int         count = getNumSeries();       // the series count
  914.         Dimension   d = getSize();                // the dimension of the canvas
  915.         ResourceBundle conn = ResourceBundle.getBundle("com.symantec.itools.swing.JChartResourceBundle");
  916.  
  917.         if ( count <= 0 )
  918.             return;
  919.  
  920.         for ( int i = 0; i < count; i++ )        // find the length of the longest series name
  921.         {
  922.             String name = getSeriesName( i );
  923.  
  924.             if ( name == "" || name == " " )
  925.                 name = conn.getString(JChartResourceBundle.JCHART_SERIES_KEY) + " " + (i+1);
  926.             maxStrLen = Math.max( fm.stringWidth( name ), maxStrLen );
  927.         }
  928.         maxStrLen = Math.max( maxStrLen, fm.stringWidth("Legend "));
  929.  
  930.         legendBorder = maxStrLen + 22;
  931.  
  932.         boxHeight = count * (lnHeight + 4) + 12;
  933.  
  934.         while ( boxHeight + lnHeight + 4 > d.height )
  935.         {
  936.             boxHeight -= (lnHeight + 4);
  937.             count--;
  938.         }
  939.  
  940.         int yPos = (d.height / 2) - (boxHeight/2) - 4;// the upper left corner of the legend (centered)
  941.  
  942.         lRect = new Rectangle( d.width - graphBorder.right - legendBorder + fm.getHeight()/2 + 2, yPos +4,
  943.                                legendBorder - fm.getHeight()/2 - 4, boxHeight );
  944.         g.drawString( conn.getString(JChartResourceBundle.JCHART_LEGEND_KEY), lRect.x + (lRect.width - fm.stringWidth( "Legend" ))/2, lRect.y - 5 );
  945.         g.drawRect( lRect.x, lRect.y, lRect.width, lRect.height - 10 );
  946.  
  947.         int ovalSize = fm.getHeight()/4;
  948.         ovalSize = ovalSize < 4 ? 4 : ovalSize;
  949.         for ( int j = 0; j < count; j++ )
  950.         {
  951.             yPos += lnHeight + 4;
  952.             g.setColor( getSeriesColor( j ));
  953.             g.fillOval( lRect.x + 6 - (ovalSize/2), yPos - (lnHeight / 2 ), ovalSize, ovalSize );
  954.             g.setColor( Color.black );
  955.             String s = getSeriesName( j );
  956.             if ( s == "" || s == " " )
  957.                 s = "Series " + (j+1);
  958.             g.drawString( s, lRect.x + 10, yPos );
  959.         }
  960.     }
  961.  
  962.     protected void drawPieSeries( java.awt.Graphics g, int seriesNum, Rectangle frame, int startAngle, int vLabel )
  963.     {
  964.         double total = seriesSum( seriesNum );
  965.         double err = 0;
  966.  
  967.         if ( getNumSeries() <= 0 || total == 0 )
  968.             return;
  969.  
  970.         Font oldFont = g.getFont();
  971.         int  size = oldFont.getSize();
  972.         int  style= oldFont.getStyle();
  973.         String fnt= oldFont.getName();
  974.  
  975.         Font newFont = new Font( fnt, graphStyle, 9 );
  976.         g.setFont( newFont );
  977.  
  978.         for ( int i = 0; i < getSeriesSize( seriesNum ); i++ )
  979.         {
  980.             double data = getSeriesValue( seriesNum, i );
  981.             double percent = data / total;
  982.             int arc = (int)((percent * 360)+.5);
  983.             err += (percent * 360) - arc;
  984.  
  985.             if ( arc >= 1 )
  986.             {
  987.                 arc++;  err--;
  988.             }
  989.             else if ( arc <= -1 )
  990.             {
  991.                 arc--; err++;
  992.             }
  993.  
  994.             g.setColor( (java.awt.Color) getSeriesColor( seriesNum, i ));
  995.             //g.setColor( (java.awt.Color) graphColors.elementAt( (( seriesNum * graphCols ) + i ) % graphColors.size()));
  996.             g.fillArc( frame.x, frame.y, frame.width, frame.height - vLabel, startAngle, arc );
  997.             if ( arc > 3 )  // do not label segments less than 3 degrees
  998.             {
  999.                g.setColor( Color.black );
  1000.  
  1001.                int tic = startAngle + arc / 2;
  1002.                double x = Math.cos((double)tic*(Math.PI/180)) * ((frame.width/2)+10);
  1003.                double y = Math.sin((double)tic*(Math.PI/180)) * ((frame.width/2)+10);
  1004.  
  1005.                g.drawString( getDataLabel( seriesNum, i ), (int)x + frame.width/2 + frame.x - 2,  frame.width/2 + frame.y - (int)y + 5 );
  1006.             }
  1007.             startAngle += arc;
  1008.         }
  1009.  
  1010.         g.setFont( oldFont );
  1011.         g.setColor( Color.black );
  1012.         g.drawOval( frame.x, frame.y, frame.width, frame.height - vLabel );
  1013.  
  1014.         FontMetrics fm = g.getFontMetrics();
  1015.         g.drawString( getSeriesName( seriesNum ), (frame.width - fm.stringWidth( getSeriesName( seriesNum )))/2 + frame.x,
  1016.                       frame.y + frame.height );
  1017.     }
  1018.  
  1019.     protected void drawTitle( java.awt.Graphics g )
  1020.     {
  1021.         Dimension    d = getSize();
  1022.         FontMetrics fm = g.getFontMetrics();
  1023.  
  1024.         g.drawString( title, (d.width - graphBorder.right - graphBorder.right - legendBorder - labelYInset - fm.stringWidth( title ))/2
  1025.                         + graphBorder.left + labelYInset, titleBorder );
  1026.     }
  1027.  
  1028.     protected void drawGrid( java.awt.Graphics g )
  1029.     {
  1030.        if ( showGrid &&  graphStyle != PIE_STYLE )
  1031.        {
  1032.             Dimension    d = getSize();                // the dimension of the canvas
  1033.             Color oldColor = g.getColor();
  1034.             g.setColor( Color.lightGray );
  1035.  
  1036.             for ( int i = 1; i <= numTicksY; i++ )
  1037.                 g.drawLine( graphBorder.left + labelYInset + 2, d.height - graphBorder.bottom - gridIncrementV * i,
  1038.                     d.width - graphBorder.right - legendBorder, d.height - graphBorder.bottom - gridIncrementV * i);
  1039.  
  1040.  
  1041.  
  1042.             int tickPosH = graphBorder.left + labelYInset;
  1043.             for ( int i = 1; i <= (graphStyle == BAR_STYLE ? graphCols : graphCols-1 ); i++ )
  1044.             {
  1045.                 g.drawLine( tickPosH + gridIncrementH, d.height - graphBorder.bottom - 2, tickPosH + gridIncrementH, d.height - graphBorder.bottom + 2 );
  1046.                 g.drawLine( tickPosH + gridIncrementH, d.height - graphBorder.bottom - 3, tickPosH + gridIncrementH, graphBorder.top  );
  1047.                 tickPosH += gridIncrementH;
  1048.             }
  1049.             g.setColor( oldColor );
  1050.         }
  1051.     }
  1052.  
  1053.     protected void drawAxis( java.awt.Graphics g )
  1054.     {
  1055.         Dimension   d = getSize();                // the dimension of the canvas
  1056.  
  1057.         g.setColor( Color.black );
  1058.  
  1059.         g.drawLine( graphBorder.left + labelYInset, graphBorder.top, graphBorder.left + labelYInset, d.height - graphBorder.bottom + 3 ); // y axis
  1060.         g.drawLine( graphBorder.left + labelYInset -3, d.height - graphBorder.bottom, d.width - graphBorder.right - legendBorder, d.height - graphBorder.bottom );
  1061.  
  1062.         drawYAxisTicks( g  );
  1063.  
  1064.         // draw ticks evenly distributed on x axis for series values
  1065.         int tickPosH = graphBorder.left + labelYInset;
  1066.         for ( int i = 1; i <= (graphStyle != BAR_STYLE ? graphCols-1 : graphCols ); i++ )
  1067.         {
  1068.             g.drawLine( tickPosH + gridIncrementH, d.height - graphBorder.bottom - 2, tickPosH + gridIncrementH, d.height - graphBorder.bottom + 2 );
  1069.             tickPosH += gridIncrementH;
  1070.         }
  1071.     }
  1072.  
  1073.     protected void drawYAxisTicks( java.awt.Graphics g )
  1074.     {
  1075.         g.setColor( Color.black );
  1076.         FontMetrics fm = g.getFontMetrics();
  1077.         Dimension    d = getSize(); // the dimension of the canvas
  1078.  
  1079.         if ( graphCols > 0 )
  1080.             for ( int i = 0; i <= numTicksY; i++ )
  1081.             {
  1082.                 g.drawLine( graphBorder.left + labelYInset - 2, d.height - graphBorder.bottom - gridIncrementV * i,
  1083.                     graphBorder.left + labelYInset + 2, d.height - graphBorder.bottom - gridIncrementV * i );
  1084.  
  1085.                 String s = truncDataStr( String.valueOf( gridStepV * i + (int)graphLower ));
  1086.                 g.drawString( s, graphBorder.left + labelYInset - fm.stringWidth( s ) - 2,
  1087.                           d.height - graphBorder.bottom - gridIncrementV * i + 3 );
  1088.             }
  1089.     }
  1090.  
  1091.     /********** Utility Routines **********/
  1092.     void debugStr( String msg )
  1093.     {
  1094.         if ( debug )
  1095.             System.out.println( msg );
  1096.     }
  1097.  
  1098.     String styleString(  )
  1099.     {
  1100.         if ( graphStyle == LINE_STYLE )
  1101.             return "line";
  1102.         else if ( graphStyle == BAR_STYLE )
  1103.             return "bar";
  1104.         else if ( graphStyle == SCATTER_STYLE )
  1105.             return "scatter";
  1106.         else if ( graphStyle == AREA_STYLE )
  1107.             return "area";
  1108.         else if ( graphStyle == PIE_STYLE )
  1109.             return "pie";
  1110.         return "other";
  1111.     }
  1112. }